﻿#region References

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Security;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.Win32;
using SpoolFileLibrary.Reader.Emf;
using SpoolFileLibrary.Reader.Hpgl;
using SpoolFileLibrary.Reader.Pcl;
using SpoolFileLibrary.Reader.Shadow;

#endregion

namespace SpoolFileLibrary.Reader {
    public class SpoolFileReader : IDisposable {
        private const string DEFAULT_SPOOLDIRECTORY_REGISTRY_KEY = @"SYSTEM\CurrentControlSet\Control\Print\Printers";
        private const string DEFAULT_SPOOLDIRECTORY_VALUENAME = "DefaultSpoolDirectory";
        private const int MAX_JOBID = 99999;
        private const int MAX_ZERO_SUFFIX = 5;
        private const string SPOOLFILE_EXTENSTION = ".spl";
        private const string SHADOWFILE_EXTENSTION = ".shd";
        private readonly string spoolFilePath = "";

        private static readonly string defaultSpoolDirectory = GetDefaultSpoolDirectory();

        private readonly Stream spoolFileStream;
        private Stream shadowFileStream;

        public SpoolFileReader(Stream spoolFileStream) {
            this.spoolFileStream = spoolFileStream;
        }

        public SpoolFileReader(string spoolFilePath) {
            if(string.IsNullOrEmpty(spoolFilePath)) {
                throw new ArgumentNullException("spoolFilePath");
            }
            this.spoolFilePath = spoolFilePath;
            this.spoolFileStream = new FileStream(spoolFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
        }

        public SpoolFileReader(int jobId)
            : this(GetSpoolfilePath(jobId)) {
        }

        #region IDisposable Members

        public void Dispose() {
            this.Close();
        }

        #endregion

        public virtual void Close() {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing) {
            if(!disposing) {
                return;
            }
            if(this.spoolFileStream != null) {
                this.spoolFileStream.Close();
            }
            if (this.shadowFileStream != null) {
                this.shadowFileStream.Close();
            }
        }

        public IShadowFile ParseShadowFile() {
            string path = Path.ChangeExtension(spoolFilePath, SHADOWFILE_EXTENSTION);
            if (File.Exists(path)) {
                shadowFileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);

                var spoolFile = (IShadowFile)this.GetSpoolFile(SpoolFileFormat.Shadow);
                return spoolFile.Parse(false, 0) ? spoolFile : null;
            }
            return null;
        }

        public ISpoolFile ParseSpoolFile() {
            return this.ParseSpoolFile(false, 0);
        }

        public ISpoolFile ParseSpoolFile(bool parsePages, int scaleFactor) {
            SpoolFileFormat fileFormat = this.GetDataFormat();
            ISpoolFile spoolFile = this.GetSpoolFile(fileFormat);
            return spoolFile.Parse(parsePages, scaleFactor) ? spoolFile : null;
        }

        private SpoolFileFormat GetDataFormat() {
            this.spoolFileStream.Seek(0, SeekOrigin.Begin);
            try {
                if(this.spoolFileStream.Length < 4) {
                    return SpoolFileFormat.RAW;
                }
                var buffer = new byte[4];
                this.spoolFileStream.Read(buffer, 0, 4);
                uint emfVersion = BitConverter.ToUInt32(buffer, 0);
                if(emfVersion.Equals(0x00010000)) {
                    return SpoolFileFormat.EMF;
                }
            } finally {
                this.spoolFileStream.Seek(0, SeekOrigin.Begin);
            }
            return SpoolFileFormat.RAW;
        }

        public byte[] GetSpoolFileData() {
            var length = (int) this.spoolFileStream.Length;
            var buffer = new byte[length];
            int sum = 0;
            try {
                int count;
                while((count = this.spoolFileStream.Read(buffer, sum, length - sum)) > 0) {
                    sum += count;
                }
            } finally {
                this.spoolFileStream.Seek(0, SeekOrigin.Begin);
            }
            return buffer;
        }

        public static Stream GetSpoolFileStream(string spoolFilePath) {
            var fileInfo = new FileInfo(spoolFilePath);
            if(fileInfo.Exists) {
                return fileInfo.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
            }
            return Stream.Null;
        }

        public static Stream GetSpoolFileStream(int jobId) {
            return GetSpoolFileStream(GetSpoolfilePath(jobId));
        }


        private ISpoolFile GetSpoolFile(SpoolFileFormat spoolFileFormat) {
            switch(spoolFileFormat) {
                case SpoolFileFormat.RAW:
                    PrintjobLanguage printjobLanguage = this.GetPrintjobLanguage();
                    switch(printjobLanguage) {
                        case PrintjobLanguage.HPGL:
                            return new HPGLSpoolFile(this.spoolFileStream);
                        case PrintjobLanguage.HPGL2:
                            break;
                        case PrintjobLanguage.HPRTL:
                            break;
                        case PrintjobLanguage.PS:
                            break;
                        case PrintjobLanguage.PCL3:
                            return new PCL3SpoolFile(this.spoolFileStream);
                        case PrintjobLanguage.PCL5:
                            return new PCL5SpoolFile(this.spoolFileStream);
                        case PrintjobLanguage.PCLXL:
                            return new PCLXLSpoolFile(this.spoolFileStream);
                    }
                    break;
                case SpoolFileFormat.EMF:
                    return new EmfSpoolFile(this.spoolFileStream);
                case SpoolFileFormat.Shadow:
                    return new ShadowFile(this.shadowFileStream);
            }
            return null;
        }

        internal static IDictionary<string, string> GetPJLSectionValues(StreamReader streamReader) {
            var pjlValues = new Dictionary<string, string>();

            streamReader.BaseStream.Seek(0, SeekOrigin.Begin);
            const string pattern = @"@PJL\s.*$";

            while(!streamReader.EndOfStream) {
                string line = streamReader.ReadLine();
                Match match = Regex.Match(line, pattern);
                if(!match.Success) {
                    continue;
                }
                string[] keyValue = match.Value.Split('=');
                if(keyValue.Length == 2) {
                    string key = keyValue[0].Trim().Substring(keyValue[0].LastIndexOf(' ')).Trim().ToUpperInvariant();
                    string value = keyValue[1].Trim().ToUpperInvariant();
                    pjlValues.Add(key, value);
                }
            }
            return pjlValues;
        }

        private PrintjobLanguage GetPrintjobLanguage() {
            var StreamReader = new StreamReader(this.spoolFileStream);
            IDictionary<string, string> pjlValues = GetPJLSectionValues(StreamReader);
            if(pjlValues.ContainsKey("LANGUAGE")) {
                switch(pjlValues["LANGUAGE"]) {
                    case "HPGL":
                        return PrintjobLanguage.HPGL;
                    case "HPGL2":
                        return PrintjobLanguage.HPGL2;
                    case "HP-RTL":
                        return PrintjobLanguage.HPRTL;
                    case "PCL":
                        return PrintjobLanguage.PCL5;
                    case "PCLXL":
                        return PrintjobLanguage.PCLXL;
                    case "PS":
                        return PrintjobLanguage.PS;
                }
            }
            return PrintjobLanguage.UNKNOWN;
        }

        private static string GetDefaultSpoolDirectory() {
            try {
                RegistryKey key = Registry.LocalMachine.OpenSubKey(DEFAULT_SPOOLDIRECTORY_REGISTRY_KEY, false);
                if(key != null) {
                    return key.GetValue(
                        DEFAULT_SPOOLDIRECTORY_VALUENAME).ToString();
                }
            } catch(SecurityException se) {
                Debug.WriteLine(se);
                throw;
            } catch(UnauthorizedAccessException uae) {
                Debug.WriteLine(uae);
                throw;
            }
            return string.Empty;
        }

        public static string GetSpoolfilePath(int jobId) {
            if(jobId <= 0 || jobId >= MAX_JOBID) {
                throw new ArgumentOutOfRangeException("jobId");
            }

            var spoolFile = new StringBuilder();
            for(int i = 0; i < MAX_ZERO_SUFFIX - jobId.ToString().Length; i++) {
                spoolFile.Append("0");
            }
            spoolFile.Append(jobId.ToString());
            spoolFile.Append(SPOOLFILE_EXTENSTION);

            return Path.Combine(defaultSpoolDirectory, spoolFile.ToString());
        }
    }
}